library(tidyverse)
library(tidymodels)
library(baguette)
library(dplyr)
library(spotifyr)
library(genius)
library(ggridges)
library(ggthemes)

Introduction

Welcome to the my final STAT 228 project! This time, I’ll be analyzing my own listening history data from Spotify. One of my favotrite aspects of Spotify is its ability to make song suggestions and generate playlists based on your listening history. I love the playlists it generates for me and I look forward to Spotify Wrapped every year!

In this project, I’ll spend some time looking at my listening history itself. Then, in keeping with Spotify’s most iconic feature, I’ll build a model and predict some aspects of my listening history!

For this project, I’ll be using the spotifyr package from TidyTuesday. To access my own data, I followed an incredibly helpful tutorial from Charlie Thompson who also contributed towards the spotifyr package.

Let’s get started!

Setting up Spotify

My face when I found out you could access your own Spotify data

First, I had to set up a Dev account with Spotify to access their Web API, giving me access to the IDs needed to pull my access token and, in doing so, my Spotify data. More detailed instructions for this are on Charlie Thompson’s tutorial. Once the access token is pulled, every subsequent Spotify function will refer to it; no need to call it every time.

The code should look like this:

Sys.setenv(SPOTIFY_CLIENT_ID = 'xxxxxxxxxxxxxxxxxxxxx')
Sys.setenv(SPOTIFY_CLIENT_SECRET = 'xxxxxxxxxxxxxxxxxxxxx')

access_token <- get_spotify_access_token()

Taking a peek at my data

Now that my Spotify is all set up, let’s see who my favorite artists are.

get_my_top_artists_or_tracks(type = 'artists', 
                             time_range = 'long_term', limit = 5) %>% 
    select(name, genres) %>% 
    rowwise %>% 
    mutate(genres = paste(genres, collapse = ', ')) %>% 
    ungroup 
## # A tibble: 5 × 2
##   name          genres                                                          
##   <chr>         <chr>                                                           
## 1 David Bowie   art rock, classic rock, glam rock, permanent wave, rock         
## 2 Amy Winehouse british soul, indie r&b, neo soul                               
## 3 Gorillaz      alternative hip hop                                             
## 4 Ariana Grande dance pop, pop                                                  
## 5 Talking Heads art punk, art rock, dance rock, funk rock, new wave, permanent …

Ok, this is pretty cool. Let’s look at my top songs now!

get_my_top_artists_or_tracks(type = 'tracks', time_range = 'long_term', limit = 5) %>% 
    mutate(artist.name = map_chr(artists, function(x) x$name[1])) %>% 
    select(name, artist.name, album.name)
##                                 name    artist.name
## 1                        Amy Amy Amy  Amy Winehouse
## 2                         Doing Yoga   Kazy Lambist
## 3                         Toi Et Moi        Paradis
## 4 Station to Station - 2016 Remaster    David Bowie
## 5                             Fed Up Stephen Marley
##                           album.name
## 1                              Frank
## 2       On You (Radio Edit) - Single
## 3                        Recto Verso
## 4 Station to Station (2016 Remaster)
## 5                       Mind Control

As much as I love all five of these songs, I’m a little surprised by them. Well, the data doesn’t lie! Since Amy is one of my top artists and is at the very top of my most-played songs, let’s start with her.

Comparing my favorite artists

Amy Winehouse

First, I’ll create the winehouse dataset that includes all information that Spotify has about her music. Next, let’s look at valence, a Spotify statistic for measuring the joy of a song; higher valence = more joyful.

winehouse <- get_artist_audio_features('Amy Winehouse')
winehouse %>% 
    arrange(-valence) %>% 
    select(track_name, valence) %>% 
    head(5) 
##                                              track_name valence
## 1  Monkey Man - Live On Jools Holland Hootenanny / 2006   0.926
## 2                                            Monkey Man   0.923
## 3                                  You're Wondering Now   0.917
## 4 Tenderly - Live On Later... With Jools Holland / 2006   0.886
## 5                                      Stronger Than Me   0.855

Now, let’s take a look at valence for each of her albums.

winehouse %>% 
  filter(album_name %in% c("Frank", "Back To Black", "Lioness: Hidden Treasures", "AMY (Original Motion Picture Soundtrack)", "At The BBC")) %>% 
ggplot(aes(x = valence, y = fct_reorder(album_name, album_release_year))) + 
    geom_density_ridges(color = "palevioletred3", fill = "palevioletred2") + 
    labs(title = "Joyplot of Amy Winehouse's joy distributions", subtitle = "Based on each album's valence", 
            x = "Valence (joy)", y = "") +
  theme_minimal()

This is interesting — valence is simlar for most albums but AMY, the motion picture soundtrack. Having seen the movie, this makes a lot of sense. Amy Winehouse died in 2011, shortly after Lioness: Hidden Treasures was released. AMY is a documentary about her life, success, and tragic downfall. It makes sense that the music selected for the film would, therefore, be a lot less joyful.

David Bowie

Valence

David Bowie, my top-listened-to artist for years, can’t escape my data analysis! Let’s do the same thing, comparing the valence of his studio albums.

bowie <- get_artist_audio_features('david bowie')

bowie %>% 
  ggplot(aes(x = valence, y = fct_reorder(album_name, album_release_year))) + 
    geom_density_ridges() + 
    labs(title = "Joyplot of David Bowie's joy distributions", subtitle = "Based on each album's valence", 
            x = "Valence (joy)", y = "") +
  theme_minimal()

Woah… that’s a lot of albums. To be fair, David Bowie was an extremely prolific artist; Amy Winehouse only had 5 albums on her Spotify but also died very young at 27. David Bowie’s career spanned 6 decades, not including posthumous releases.

Let’s modify this graph slightly to reflect year rather than album name. The album names themselves don’t add much to this visualization. I think it would be more interesting to simplify and emphasize the change in valence over time.

bowie %>% 
  mutate(decade = floor(album_release_year/10) * 10) %>% 
  group_by(decade) %>% 
  #summarize(valence_by_decade = mean(valence)) %>% 
  ggplot(aes(x = valence, y = factor(decade))) + 
    geom_density_ridges(aes(fill = decade, color = decade)) + 
    labs(title = "Joyplot of David Bowie's joy distributions", subtitle = "Based on each decade's valence", 
            x = "Valence (joy)", y = "") +
  scale_y_discrete(labels = c("1960s", "1970s", "1980s", "1990s", "2000s", "2010s", "2020s")) +
  theme_minimal() +
  theme(legend.position = "none")

Now this looks much better! Because of the sheer number of albums David Bowie has produced, plotting valence by decade makes a lot of sense. From the visualization above, it looks like his career has remained fairly constant. Valence (joyfulness) of his music started higher and slowly dipped, looking lowest in the 80s before coming back up a little. That’s surprising since the 80s were full of upbeat, danceable music, including Bowie’s Let’s Dance album from 1983.

Winehouse VS Bowie

Before we move on to modeling, it’s probably worthwhile to compare the two musicians we’ve looked at so far. Let’s compare their overall valence.

ggplot() + 
  geom_density(data = bowie, aes(x = valence), color = "slateblue", fill = "slateblue", alpha = 0.5) +
  geom_density(data = winehouse, aes(x = valence), color = "royalblue", fill = "royalblue", alpha = 0.5) +
  labs(title = "Amy Winehouse VS David Bowie's Music Valence", x = "Valence (joy)", y = "") +
  annotate("text", x = 0.68, y = 1.95, size = 4,
           label = "Amy Winehouse", color = "royalblue") +
  annotate("text", x = 0.39, y = 1.75, size = 4,
           label = "David Bowie", color = "slateblue") +
  theme_minimal() +
  theme(axis.ticks.y = element_blank(), axis.text.y = element_blank())

Amy Winehouse has two distinctive peaks while David Bowie has one. I’m guessing that AMY definitely contributed to the height of that lower peak. Regardless, Amy Winehouse’s music seems to be a little more joyful than David Bowie’s.

Modeling

So far, we’ve looked a lot at valence, Spotify’s measure of a song’s joyfulness. The focus has primarily been on Amy Winehouse and David Bowie, my two top listened-to artists.

I wonder if we can build a model that predicts whether a song belongs to David Bowie or Amy Winehouse based on it’s spotify statistics: valence, danceability, speechiness,?

Creating our new dataset

First, let’s get the dataset we’ll be working with by using rbind() to add the bowie data to the winehouse data. We also have to factor artist_name in order for the classification models to work correctly.

bwjoined = rbind(winehouse, bowie)

bwjoined = bwjoined %>% 
  mutate(artist_name = factor(artist_name))

Now that we have both of their music data in one dataset, let’s start making our models!

Descision Tree

We’re going to build a decision tree by using Spotify’s statistics to predict the artist. The features we’ll be using are:

  • danceability

  • energy

  • loudness

  • speechiness

  • acousticness

  • instrumentalness

  • liveness

  • tempo

  • and, of course, valence!

Now it’s time to create the model. First, we set up by splitting the data:

# 1 Data splitting
set.seed(18) # it's a good number! 
bw_split = initial_split(bwjoined, prop = 0.7)

bw_train = training(bw_split)
bw_test = testing(bw_split)

Now, we fit the model to a classification decision tree…

# 2 Fit the model
bw_tree = 
  decision_tree() %>%
  set_mode(mode = "classification") %>%
  fit(artist_name ~ danceability + energy + loudness + speechiness + acousticness + instrumentalness + liveness + tempo + valence, data = bw_train)

…and generate predictions…

# 3 Generate predictions
bw_pred = bw_tree %>%
  predict(new_data = bw_test)

…and finally, generate prediction metrics.

# 4 Prediction metrics
bw_test %>%
  mutate(artist_pred = bw_pred$.pred_class) %>%
  accuracy(estimate = artist_pred, truth = artist_name)
## # A tibble: 1 × 3
##   .metric  .estimator .estimate
##   <chr>    <chr>          <dbl>
## 1 accuracy binary         0.912

The accuracy of this model is 0.91 which is very high. Because the accuracy is so high, it might be worth looking at a confusion matrix to see how the predicitons are laid out.

bw_test %>% 
  mutate(artist_pred = bw_pred$.pred_class) %>%
  conf_mat(estimate = artist_pred, truth = artist_name)
##                Truth
## Prediction      Amy Winehouse David Bowie
##   Amy Winehouse            57          14
##   David Bowie              29         389

According to this confusion matrix, David Bowie is predicted correctly most of the time, but Amy Winehouse has a lower accuracy. It’s not extreme as she’s still predicted correctly the majority of the time, but it’s noticeable. This might be because David Bowie has many more songs than Amy Winehouse so the model is more inclined to guess Bowie from the start. If 80% of the dataset consists of Bowie’s music, even if the model guessed David Bowie every single time, it would still have 80% accuracy.

Let’s see if any of these other models can improve upon this issue by learning from their mistakes (literally!).

Bagged Decision Tree

A bagged decision tree is a model that builds many trees on different bootstrap samples of the training data, which hopefully lowers the model’s variance and keeps bias low.

# 1 Data splitting
set.seed(18) # it's a good number! 
bw_split = initial_split(bwjoined, prop = 0.7)

bw_train = training(bw_split)
bw_test = testing(bw_split)

# 2 Fit the model
bw_bag_tree = 
  bag_tree() %>%
  set_mode(mode = "classification") %>%
  fit(artist_name ~ danceability + energy + loudness + speechiness + acousticness + instrumentalness + liveness + tempo + valence, data = bw_train)

# 3 Generate predictions
bw_pred = bw_bag_tree %>%
  predict(new_data = bw_test)

# 4 Prediction metrics
bw_test %>%
  mutate(artist_pred = bw_pred$.pred_class) %>%
  accuracy(estimate = artist_pred, truth = artist_name)
## # A tibble: 1 × 3
##   .metric  .estimator .estimate
##   <chr>    <chr>          <dbl>
## 1 accuracy binary         0.933
bw_test %>%
  mutate(artist_pred = bw_pred$.pred_class) %>%
  conf_mat(estimate = artist_pred, truth = artist_name)
##                Truth
## Prediction      Amy Winehouse David Bowie
##   Amy Winehouse            58           5
##   David Bowie              28         398

In this bagged decision tree, the accuracy is now 0.93 but not much has changed. If anything, guess accuracy for David Bowie is higher but Amy Winehouse’s remains the same.

Let’s try a random forest next. Similar to a bagged decision tree, random forests use a random subset of features at each split in the tree.

Random Forest

# 1 Data splitting
set.seed(18) # it's a good number! 
bw_split = initial_split(bwjoined, prop = 0.7)

bw_train = training(bw_split)
bw_test = testing(bw_split)

# 2 Fit the model
bw_rf = 
  rand_forest() %>%
  set_mode(mode = "classification") %>%
  fit(artist_name ~ danceability + energy + loudness + speechiness + acousticness + instrumentalness + liveness + tempo + valence, data = bw_train)

# 3 Generate predictions
bw_pred = bw_rf %>%
  predict(new_data = bw_test)

# 4 Prediction metrics
bw_test %>%
  mutate(artist_pred = bw_pred$.pred_class) %>%
  accuracy(estimate = artist_pred, truth = artist_name)
## # A tibble: 1 × 3
##   .metric  .estimator .estimate
##   <chr>    <chr>          <dbl>
## 1 accuracy binary         0.933
bw_test %>%
  mutate(artist_pred = bw_pred$.pred_class) %>%
  conf_mat(estimate = artist_pred, truth = artist_name)
##                Truth
## Prediction      Amy Winehouse David Bowie
##   Amy Winehouse            56           3
##   David Bowie              30         400

With a similar outcome as the bagged tree, random forests produce a very similar output.

Gradient Boosting Model (GBM)

Gradient Boosting Models (GBMs) have become a very popular model. This is because they begin with a very simple tree and learn from the mistakes of the previous tree in the ensemble (previously generated tree in the GBM). They have options that we can tweak but we’ll keep it simple.

# 1 Data splitting
set.seed(18) # it's a good number! 
bw_split = initial_split(bwjoined, prop = 0.7)

bw_train = training(bw_split)
bw_test = testing(bw_split)

# 2 Fit the model
bw_boost = 
  boost_tree() %>%
  set_mode(mode = "classification") %>%
  fit(artist_name ~ danceability + energy + loudness + speechiness + acousticness + instrumentalness + liveness + tempo + valence, data = bw_train)

# 3 Generate predictions
bw_pred = bw_boost %>%
  predict(new_data = bw_test)

# 4 Prediction metrics
bw_test %>%
  mutate(artist_pred = bw_pred$.pred_class) %>%
  accuracy(estimate = artist_pred, truth = artist_name)
## # A tibble: 1 × 3
##   .metric  .estimator .estimate
##   <chr>    <chr>          <dbl>
## 1 accuracy binary         0.937
bw_test %>%
  mutate(artist_pred = bw_pred$.pred_class) %>%
  conf_mat(estimate = artist_pred, truth = artist_name)
##                Truth
## Prediction      Amy Winehouse David Bowie
##   Amy Winehouse            57           2
##   David Bowie              29         401

This model definitely has the highest accuracy at 0.94. This model is fantastic at predicting David Bowie’s music but not much change has happened with Amy Winehouse’s music. Again, I attribute part of this to the difference in sample sizes but her music could just be more difficult to predict.

Bonus: Adding More Artists!

Just for fun, let’s use a GBM again to predict which artist is which… but try it across all five of my top artists!

First, to set up the dataset:

# Get the data from my other three top artists
gorillaz <- get_artist_audio_features('Gorillaz')
grande <- get_artist_audio_features('Ariana Grande')
theads <- get_artist_audio_features('Talking Heads')

# Combine all of the artists' data
all_artists = rbind(winehouse, bowie, gorillaz, grande, theads)

# Factor artist_name to use it for our GBM!
all_artists = all_artists %>% 
  mutate(artist_name = factor(artist_name))

Now that the data is all set, we can fit our GBM!

# 1 Data splitting
set.seed(18) # it's a good number! 
all_split = initial_split(all_artists, prop = 0.7)

all_train = training(all_split)
all_test = testing(all_split)

# 2 Fit the model
all_boost = 
  boost_tree() %>%
  set_mode(mode = "classification") %>%
  fit(artist_name ~ danceability + energy + loudness + speechiness + acousticness + instrumentalness + liveness + tempo + valence, data = all_train)

# 3 Generate predictions
all_pred = all_boost %>%
  predict(new_data = all_test)

# 4 Prediction metrics
all_test %>%
  mutate(artist_pred = all_pred$.pred_class) %>%
  accuracy(estimate = artist_pred, truth = artist_name)
## # A tibble: 1 × 3
##   .metric  .estimator .estimate
##   <chr>    <chr>          <dbl>
## 1 accuracy multiclass     0.839
all_test %>%
  mutate(artist_pred = all_pred$.pred_class) %>%
  conf_mat(estimate = artist_pred, truth = artist_name)
##                Truth
## Prediction      Amy Winehouse Ariana Grande David Bowie Gorillaz Talking Heads
##   Amy Winehouse            56             2           5        3             0
##   Ariana Grande             6           126           7        1             2
##   David Bowie              18             1         372       23            27
##   Gorillaz                  3             2           7       55             1
##   Talking Heads             3             0          11        9            72

The accuracy is 0.84. That’s not all that bad, actually! In this case, since we have so many more targets (artists), the confusion matrix suits it name — it’s pretty confusing. Still, I can see that Ariana Grande and David Bowie have the highest accuracy. In one more test, I wonder how a model would do if it just had to look at those two musicians?

Bonus II: I Can’t Stop Modeling!

Let’s just do this all in one go:

# Combine all of the artists' data
bowie_grande = rbind(bowie, grande)

# Factor artist_name to use it for our GBM!
bowie_grande = bowie_grande %>% 
  mutate(artist_name = factor(artist_name))

# 1 Data splitting
set.seed(18) # it's a good number! 
bg_split = initial_split(bowie_grande, prop = 0.7)

bg_train = training(bg_split)
bg_test = testing(bg_split)

# 2 Fit the model
bg_boost = 
  boost_tree() %>%
  set_mode(mode = "classification") %>%
  fit(artist_name ~ danceability + energy + loudness + speechiness + acousticness + instrumentalness + liveness + tempo + valence, data = bg_train)

# 3 Generate predictions
bg_pred = bg_boost %>%
  predict(new_data = bg_test)

# 4 Prediction metrics
bg_test %>%
  mutate(artist_pred = bg_pred$.pred_class) %>%
  accuracy(estimate = artist_pred, truth = artist_name)
## # A tibble: 1 × 3
##   .metric  .estimator .estimate
##   <chr>    <chr>          <dbl>
## 1 accuracy binary         0.983
bg_test %>%
  mutate(artist_pred = bg_pred$.pred_class) %>%
  conf_mat(estimate = artist_pred, truth = artist_name)
##                Truth
## Prediction      Ariana Grande David Bowie
##   Ariana Grande           123           3
##   David Bowie               6         407

0.98 accuracy!!! That’s darn good, especially since the confusion matrix isn’t sketchy about it!

Conclusion

Throughout this final project, a lot of cool stats were analyzed. I learned about Winehouse, Bowie, and how distinct some of the music that I listen to can be — David Bowie and Ariana Grande in particular.

I hope this data analysis was interesting and I encourage you to analyze your own Spotify data! Thank you for reading this post and I hope you have a great day!

LS0tCnRpdGxlOiAiV2UgQ291bGQgQmUgRGF0YSBTY2llbnRpc3RzLCBKdXN0IEZvciBPbmUgRGF5Li4uIgphdXRob3I6ICJLaWtpIFJlZ2FuIgpkYXRlOiAiTWF5IDEyLCAyMDIyIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogZmxhdGx5CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodGlkeW1vZGVscykKbGlicmFyeShiYWd1ZXR0ZSkKbGlicmFyeShkcGx5cikKbGlicmFyeShzcG90aWZ5cikKbGlicmFyeShnZW5pdXMpCmxpYnJhcnkoZ2dyaWRnZXMpCmxpYnJhcnkoZ2d0aGVtZXMpCmBgYAoKIyBJbnRyb2R1Y3Rpb24KCldlbGNvbWUgdG8gdGhlIG15IGZpbmFsIFNUQVQgMjI4IHByb2plY3QhIFRoaXMgdGltZSwgSSdsbCBiZSBhbmFseXppbmcgKm15IG93biogbGlzdGVuaW5nIGhpc3RvcnkgZGF0YSBmcm9tIFNwb3RpZnkuIE9uZSBvZiBteSBmYXZvdHJpdGUgYXNwZWN0cyBvZiBTcG90aWZ5IGlzIGl0cyBhYmlsaXR5IHRvIG1ha2Ugc29uZyBzdWdnZXN0aW9ucyBhbmQgZ2VuZXJhdGUgcGxheWxpc3RzIGJhc2VkIG9uIHlvdXIgbGlzdGVuaW5nIGhpc3RvcnkuIEkgbG92ZSB0aGUgcGxheWxpc3RzIGl0IGdlbmVyYXRlcyBmb3IgbWUgYW5kIEkgbG9vayBmb3J3YXJkIHRvIFNwb3RpZnkgV3JhcHBlZCBldmVyeSB5ZWFyISAKCkluIHRoaXMgcHJvamVjdCwgSSdsbCBzcGVuZCBzb21lIHRpbWUgbG9va2luZyBhdCBteSBsaXN0ZW5pbmcgaGlzdG9yeSBpdHNlbGYuIFRoZW4sIGluIGtlZXBpbmcgd2l0aCBTcG90aWZ5J3MgbW9zdCBpY29uaWMgZmVhdHVyZSwgSSdsbCBidWlsZCBhIG1vZGVsIGFuZCAqKnByZWRpY3QqKiBzb21lIGFzcGVjdHMgb2YgbXkgbGlzdGVuaW5nIGhpc3RvcnkhCgpGb3IgdGhpcyBwcm9qZWN0LCBJJ2xsIGJlIHVzaW5nIHRoZSBgc3BvdGlmeXJgIHBhY2thZ2UgZnJvbSBbVGlkeVR1ZXNkYXldKGh0dHBzOi8vZ2l0aHViLmNvbS9yZm9yZGF0YXNjaWVuY2UvdGlkeXR1ZXNkYXkvYmxvYi9tYXN0ZXIvZGF0YS8yMDIwLzIwMjAtMDEtMjEvcmVhZG1lLm1kKS4gVG8gYWNjZXNzIG15IG93biBkYXRhLCBJIGZvbGxvd2VkIGFuIGluY3JlZGlibHkgaGVscGZ1bCB0dXRvcmlhbCBmcm9tIFtDaGFybGllIFRob21wc29uXShodHRwczovL3d3dy5yY2hhcmxpZS5jb20vc3BvdGlmeXIvKSB3aG8gYWxzbyBjb250cmlidXRlZCB0b3dhcmRzIHRoZSBgc3BvdGlmeXJgIHBhY2thZ2UuCgpMZXQncyBnZXQgc3RhcnRlZCEKCiMjIFNldHRpbmcgdXAgU3BvdGlmeQoKIVsqTXkgZmFjZSB3aGVuIEkgZm91bmQgb3V0IHlvdSBjb3VsZCBhY2Nlc3MgeW91ciBvd24gU3BvdGlmeSBkYXRhKl0oaHR0cDovL3d3dy5yZWFjdGlvbmdpZnMuY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE0LzAxL1Nob2NrZWQtRGF2aWQtQm93aWUuZ2lmKQoKRmlyc3QsIEkgaGFkIHRvIHNldCB1cCBhIERldiBhY2NvdW50IHdpdGggU3BvdGlmeSB0byBhY2Nlc3MgdGhlaXIgV2ViIEFQSSwgZ2l2aW5nIG1lIGFjY2VzcyB0byB0aGUgSURzIG5lZWRlZCB0byBwdWxsIG15IGFjY2VzcyB0b2tlbiBhbmQsIGluIGRvaW5nIHNvLCBteSBTcG90aWZ5IGRhdGEuIE1vcmUgZGV0YWlsZWQgaW5zdHJ1Y3Rpb25zIGZvciB0aGlzIGFyZSBvbiBbQ2hhcmxpZSBUaG9tcHNvbidzIHR1dG9yaWFsXShodHRwczovL3d3dy5yY2hhcmxpZS5jb20vc3BvdGlmeXIvKS4gT25jZSB0aGUgYWNjZXNzIHRva2VuIGlzIHB1bGxlZCwgZXZlcnkgc3Vic2VxdWVudCBTcG90aWZ5IGZ1bmN0aW9uIHdpbGwgcmVmZXIgdG8gaXQ7IG5vIG5lZWQgdG8gY2FsbCBpdCBldmVyeSB0aW1lLgoKVGhlIGNvZGUgc2hvdWxkIGxvb2sgbGlrZSB0aGlzOgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnNwb3RpZnlfc29uZ3MgPC0gcmVhZHI6OnJlYWRfY3N2KCdodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcmZvcmRhdGFzY2llbmNlL3RpZHl0dWVzZGF5L21hc3Rlci9kYXRhLzIwMjAvMjAyMC0wMS0yMS9zcG90aWZ5X3NvbmdzLmNzdicpCgojVmlldyhzcG90aWZ5X3NvbmdzKQpgYGAKYGBge3IsIGVjaG8gPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9ClN5cy5zZXRlbnYoU1BPVElGWV9DTElFTlRfSUQgPSAnMDA2ZDk3MzQ1NDU3NDU2ZmI1OTk5MTg1NTk1N2QyNjgnKQpTeXMuc2V0ZW52KFNQT1RJRllfQ0xJRU5UX1NFQ1JFVCA9ICc4MGYzYzY5ZGU5N2M0YTFhOTY3NzM1YjhkYTQ5ODk2MycpCgphY2Nlc3NfdG9rZW4gPC0gZ2V0X3Nwb3RpZnlfYWNjZXNzX3Rva2VuKCkKYGBgCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGV2YWwgPSBGQUxTRX0KU3lzLnNldGVudihTUE9USUZZX0NMSUVOVF9JRCA9ICd4eHh4eHh4eHh4eHh4eHh4eHh4eHgnKQpTeXMuc2V0ZW52KFNQT1RJRllfQ0xJRU5UX1NFQ1JFVCA9ICd4eHh4eHh4eHh4eHh4eHh4eHh4eHgnKQoKYWNjZXNzX3Rva2VuIDwtIGdldF9zcG90aWZ5X2FjY2Vzc190b2tlbigpCmBgYAoKIyMgVGFraW5nIGEgcGVlayBhdCBteSBkYXRhCgpOb3cgdGhhdCBteSBTcG90aWZ5IGlzIGFsbCBzZXQgdXAsIGxldCdzIHNlZSB3aG8gbXkgZmF2b3JpdGUgYXJ0aXN0cyBhcmUuIAoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQpnZXRfbXlfdG9wX2FydGlzdHNfb3JfdHJhY2tzKHR5cGUgPSAnYXJ0aXN0cycsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVfcmFuZ2UgPSAnbG9uZ190ZXJtJywgbGltaXQgPSA1KSAlPiUgCiAgICBzZWxlY3QobmFtZSwgZ2VucmVzKSAlPiUgCiAgICByb3d3aXNlICU+JSAKICAgIG11dGF0ZShnZW5yZXMgPSBwYXN0ZShnZW5yZXMsIGNvbGxhcHNlID0gJywgJykpICU+JSAKICAgIHVuZ3JvdXAgCmBgYAoKT2ssIHRoaXMgaXMgcHJldHR5IGNvb2wuIExldCdzIGxvb2sgYXQgbXkgdG9wIHNvbmdzIG5vdyEKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KZ2V0X215X3RvcF9hcnRpc3RzX29yX3RyYWNrcyh0eXBlID0gJ3RyYWNrcycsIHRpbWVfcmFuZ2UgPSAnbG9uZ190ZXJtJywgbGltaXQgPSA1KSAlPiUgCiAgICBtdXRhdGUoYXJ0aXN0Lm5hbWUgPSBtYXBfY2hyKGFydGlzdHMsIGZ1bmN0aW9uKHgpIHgkbmFtZVsxXSkpICU+JSAKICAgIHNlbGVjdChuYW1lLCBhcnRpc3QubmFtZSwgYWxidW0ubmFtZSkKYGBgCgpBcyBtdWNoIGFzIEkgbG92ZSBhbGwgZml2ZSBvZiB0aGVzZSBzb25ncywgSSdtIGEgbGl0dGxlIHN1cnByaXNlZCBieSB0aGVtLiBXZWxsLCB0aGUgZGF0YSBkb2Vzbid0IGxpZSEgU2luY2UgQW15IGlzIG9uZSBvZiBteSB0b3AgYXJ0aXN0cyBhbmQgaXMgYXQgdGhlIHZlcnkgdG9wIG9mIG15IG1vc3QtcGxheWVkIHNvbmdzLCBsZXQncyBzdGFydCB3aXRoIGhlci4KCiMgQ29tcGFyaW5nIG15IGZhdm9yaXRlIGFydGlzdHMKCiMjIEFteSBXaW5laG91c2UKCkZpcnN0LCBJJ2xsIGNyZWF0ZSB0aGUgYHdpbmVob3VzZWAgZGF0YXNldCB0aGF0IGluY2x1ZGVzIGFsbCBpbmZvcm1hdGlvbiB0aGF0IFNwb3RpZnkgaGFzIGFib3V0IGhlciBtdXNpYy4gTmV4dCwgbGV0J3MgbG9vayBhdCAqKnZhbGVuY2UqKiwgYSBTcG90aWZ5IHN0YXRpc3RpYyBmb3IgbWVhc3VyaW5nIHRoZSAqKmpveSoqIG9mIGEgc29uZzsgaGlnaGVyIHZhbGVuY2UgPSBtb3JlIGpveWZ1bC4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0Kd2luZWhvdXNlIDwtIGdldF9hcnRpc3RfYXVkaW9fZmVhdHVyZXMoJ0FteSBXaW5laG91c2UnKQp3aW5laG91c2UgJT4lIAogICAgYXJyYW5nZSgtdmFsZW5jZSkgJT4lIAogICAgc2VsZWN0KHRyYWNrX25hbWUsIHZhbGVuY2UpICU+JSAKICAgIGhlYWQoNSkgCmBgYAoKTm93LCBsZXQncyB0YWtlIGEgbG9vayBhdCB2YWxlbmNlIGZvciBlYWNoIG9mIGhlciBhbGJ1bXMuCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CndpbmVob3VzZSAlPiUgCiAgZmlsdGVyKGFsYnVtX25hbWUgJWluJSBjKCJGcmFuayIsICJCYWNrIFRvIEJsYWNrIiwgIkxpb25lc3M6IEhpZGRlbiBUcmVhc3VyZXMiLCAiQU1ZIChPcmlnaW5hbCBNb3Rpb24gUGljdHVyZSBTb3VuZHRyYWNrKSIsICJBdCBUaGUgQkJDIikpICU+JSAKZ2dwbG90KGFlcyh4ID0gdmFsZW5jZSwgeSA9IGZjdF9yZW9yZGVyKGFsYnVtX25hbWUsIGFsYnVtX3JlbGVhc2VfeWVhcikpKSArIAogICAgZ2VvbV9kZW5zaXR5X3JpZGdlcyhjb2xvciA9ICJwYWxldmlvbGV0cmVkMyIsIGZpbGwgPSAicGFsZXZpb2xldHJlZDIiKSArIAogICAgbGFicyh0aXRsZSA9ICJKb3lwbG90IG9mIEFteSBXaW5laG91c2UncyBqb3kgZGlzdHJpYnV0aW9ucyIsIHN1YnRpdGxlID0gIkJhc2VkIG9uIGVhY2ggYWxidW0ncyB2YWxlbmNlIiwgCiAgICAgICAgICAgIHggPSAiVmFsZW5jZSAoam95KSIsIHkgPSAiIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKClRoaXMgaXMgaW50ZXJlc3Rpbmcg4oCUIHZhbGVuY2UgaXMgc2ltbGFyIGZvciBtb3N0IGFsYnVtcyBidXQgKkFNWSosIHRoZSBtb3Rpb24gcGljdHVyZSBzb3VuZHRyYWNrLiBIYXZpbmcgc2VlbiB0aGUgbW92aWUsIHRoaXMgbWFrZXMgYSBsb3Qgb2Ygc2Vuc2UuIEFteSBXaW5laG91c2UgZGllZCBpbiAyMDExLCBzaG9ydGx5IGFmdGVyICpMaW9uZXNzOiBIaWRkZW4gVHJlYXN1cmVzKiB3YXMgcmVsZWFzZWQuICpBTVkqIGlzIGEgZG9jdW1lbnRhcnkgYWJvdXQgaGVyIGxpZmUsIHN1Y2Nlc3MsIGFuZCB0cmFnaWMgZG93bmZhbGwuIEl0IG1ha2VzIHNlbnNlIHRoYXQgdGhlIG11c2ljIHNlbGVjdGVkIGZvciB0aGUgZmlsbSB3b3VsZCwgdGhlcmVmb3JlLCBiZSBhIGxvdCBsZXNzIGpveWZ1bC4KCiMjIERhdmlkIEJvd2llCgojIyMgVmFsZW5jZQoKRGF2aWQgQm93aWUsIG15IHRvcC1saXN0ZW5lZC10byBhcnRpc3QgZm9yIHllYXJzLCBjYW4ndCBlc2NhcGUgbXkgZGF0YSBhbmFseXNpcyEgTGV0J3MgZG8gdGhlIHNhbWUgdGhpbmcsIGNvbXBhcmluZyB0aGUgdmFsZW5jZSBvZiBoaXMgc3R1ZGlvIGFsYnVtcy4gCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmJvd2llIDwtIGdldF9hcnRpc3RfYXVkaW9fZmVhdHVyZXMoJ2RhdmlkIGJvd2llJykKCmJvd2llICU+JSAKICBnZ3Bsb3QoYWVzKHggPSB2YWxlbmNlLCB5ID0gZmN0X3Jlb3JkZXIoYWxidW1fbmFtZSwgYWxidW1fcmVsZWFzZV95ZWFyKSkpICsgCiAgICBnZW9tX2RlbnNpdHlfcmlkZ2VzKCkgKyAKICAgIGxhYnModGl0bGUgPSAiSm95cGxvdCBvZiBEYXZpZCBCb3dpZSdzIGpveSBkaXN0cmlidXRpb25zIiwgc3VidGl0bGUgPSAiQmFzZWQgb24gZWFjaCBhbGJ1bSdzIHZhbGVuY2UiLCAKICAgICAgICAgICAgeCA9ICJWYWxlbmNlIChqb3kpIiwgeSA9ICIiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKV29haC4uLiB0aGF0J3MgYSAqbG90KiBvZiBhbGJ1bXMuIFRvIGJlIGZhaXIsIERhdmlkIEJvd2llIHdhcyBhbiAqZXh0cmVtZWx5KiBwcm9saWZpYyBhcnRpc3Q7IEFteSBXaW5laG91c2Ugb25seSBoYWQgNSBhbGJ1bXMgb24gaGVyIFNwb3RpZnkgYnV0IGFsc28gZGllZCB2ZXJ5IHlvdW5nIGF0IDI3LiBEYXZpZCBCb3dpZSdzIGNhcmVlciBzcGFubmVkIDYgZGVjYWRlcywgbm90IGluY2x1ZGluZyBwb3N0aHVtb3VzIHJlbGVhc2VzLiAKCkxldCdzIG1vZGlmeSB0aGlzIGdyYXBoICpzbGlnaHRseSogdG8gcmVmbGVjdCB5ZWFyIHJhdGhlciB0aGFuIGFsYnVtIG5hbWUuIFRoZSBhbGJ1bSBuYW1lcyB0aGVtc2VsdmVzIGRvbid0IGFkZCBtdWNoIHRvIHRoaXMgdmlzdWFsaXphdGlvbi4gSSB0aGluayBpdCB3b3VsZCBiZSBtb3JlIGludGVyZXN0aW5nIHRvIHNpbXBsaWZ5IGFuZCBlbXBoYXNpemUgdGhlIGNoYW5nZSBpbiB2YWxlbmNlIG92ZXIgdGltZS4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KYm93aWUgJT4lIAogIG11dGF0ZShkZWNhZGUgPSBmbG9vcihhbGJ1bV9yZWxlYXNlX3llYXIvMTApICogMTApICU+JSAKICBncm91cF9ieShkZWNhZGUpICU+JSAKICAjc3VtbWFyaXplKHZhbGVuY2VfYnlfZGVjYWRlID0gbWVhbih2YWxlbmNlKSkgJT4lIAogIGdncGxvdChhZXMoeCA9IHZhbGVuY2UsIHkgPSBmYWN0b3IoZGVjYWRlKSkpICsgCiAgICBnZW9tX2RlbnNpdHlfcmlkZ2VzKGFlcyhmaWxsID0gZGVjYWRlLCBjb2xvciA9IGRlY2FkZSkpICsgCiAgICBsYWJzKHRpdGxlID0gIkpveXBsb3Qgb2YgRGF2aWQgQm93aWUncyBqb3kgZGlzdHJpYnV0aW9ucyIsIHN1YnRpdGxlID0gIkJhc2VkIG9uIGVhY2ggZGVjYWRlJ3MgdmFsZW5jZSIsIAogICAgICAgICAgICB4ID0gIlZhbGVuY2UgKGpveSkiLCB5ID0gIiIpICsKICBzY2FsZV95X2Rpc2NyZXRlKGxhYmVscyA9IGMoIjE5NjBzIiwgIjE5NzBzIiwgIjE5ODBzIiwgIjE5OTBzIiwgIjIwMDBzIiwgIjIwMTBzIiwgIjIwMjBzIikpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpOb3cgdGhpcyBsb29rcyBtdWNoIGJldHRlciEgQmVjYXVzZSBvZiB0aGUgc2hlZXIgbnVtYmVyIG9mIGFsYnVtcyBEYXZpZCBCb3dpZSBoYXMgcHJvZHVjZWQsIHBsb3R0aW5nIHZhbGVuY2UgYnkgZGVjYWRlIG1ha2VzIGEgbG90IG9mIHNlbnNlLiBGcm9tIHRoZSB2aXN1YWxpemF0aW9uIGFib3ZlLCBpdCBsb29rcyBsaWtlIGhpcyBjYXJlZXIgaGFzIHJlbWFpbmVkIGZhaXJseSBjb25zdGFudC4gVmFsZW5jZSAoam95ZnVsbmVzcykgb2YgaGlzIG11c2ljIHN0YXJ0ZWQgaGlnaGVyIGFuZCBzbG93bHkgZGlwcGVkLCBsb29raW5nIGxvd2VzdCBpbiB0aGUgODBzIGJlZm9yZSBjb21pbmcgYmFjayB1cCBhIGxpdHRsZS4gVGhhdCdzIHN1cnByaXNpbmcgc2luY2UgdGhlIDgwcyB3ZXJlIGZ1bGwgb2YgdXBiZWF0LCBkYW5jZWFibGUgbXVzaWMsIGluY2x1ZGluZyBCb3dpZSdzICpMZXQncyBEYW5jZSogYWxidW0gZnJvbSAxOTgzLiAKCiMgV2luZWhvdXNlIFZTIEJvd2llCgpCZWZvcmUgd2UgbW92ZSBvbiB0byBtb2RlbGluZywgaXQncyBwcm9iYWJseSB3b3J0aHdoaWxlIHRvIGNvbXBhcmUgdGhlIHR3byBtdXNpY2lhbnMgd2UndmUgbG9va2VkIGF0IHNvIGZhci4gTGV0J3MgY29tcGFyZSB0aGVpciBvdmVyYWxsICoqdmFsZW5jZSoqLgoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQpnZ3Bsb3QoKSArIAogIGdlb21fZGVuc2l0eShkYXRhID0gYm93aWUsIGFlcyh4ID0gdmFsZW5jZSksIGNvbG9yID0gInNsYXRlYmx1ZSIsIGZpbGwgPSAic2xhdGVibHVlIiwgYWxwaGEgPSAwLjUpICsKICBnZW9tX2RlbnNpdHkoZGF0YSA9IHdpbmVob3VzZSwgYWVzKHggPSB2YWxlbmNlKSwgY29sb3IgPSAicm95YWxibHVlIiwgZmlsbCA9ICJyb3lhbGJsdWUiLCBhbHBoYSA9IDAuNSkgKwogIGxhYnModGl0bGUgPSAiQW15IFdpbmVob3VzZSBWUyBEYXZpZCBCb3dpZSdzIE11c2ljIFZhbGVuY2UiLCB4ID0gIlZhbGVuY2UgKGpveSkiLCB5ID0gIiIpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSAwLjY4LCB5ID0gMS45NSwgc2l6ZSA9IDQsCiAgICAgICAgICAgbGFiZWwgPSAiQW15IFdpbmVob3VzZSIsIGNvbG9yID0gInJveWFsYmx1ZSIpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSAwLjM5LCB5ID0gMS43NSwgc2l6ZSA9IDQsCiAgICAgICAgICAgbGFiZWwgPSAiRGF2aWQgQm93aWUiLCBjb2xvciA9ICJzbGF0ZWJsdWUiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCkFteSBXaW5laG91c2UgaGFzIHR3byBkaXN0aW5jdGl2ZSBwZWFrcyB3aGlsZSBEYXZpZCBCb3dpZSBoYXMgb25lLiBJJ20gZ3Vlc3NpbmcgdGhhdCAqQU1ZKiBkZWZpbml0ZWx5IGNvbnRyaWJ1dGVkIHRvIHRoZSBoZWlnaHQgb2YgdGhhdCBsb3dlciBwZWFrLiBSZWdhcmRsZXNzLCBBbXkgV2luZWhvdXNlJ3MgbXVzaWMgc2VlbXMgdG8gYmUgYSBsaXR0bGUgbW9yZSBqb3lmdWwgdGhhbiBEYXZpZCBCb3dpZSdzLiAKCiMgTW9kZWxpbmcKClNvIGZhciwgd2UndmUgbG9va2VkIGEgbG90IGF0IHZhbGVuY2UsIFNwb3RpZnkncyBtZWFzdXJlIG9mIGEgc29uZydzIGpveWZ1bG5lc3MuIFRoZSBmb2N1cyBoYXMgcHJpbWFyaWx5IGJlZW4gb24gQW15IFdpbmVob3VzZSBhbmQgRGF2aWQgQm93aWUsIG15IHR3byB0b3AgbGlzdGVuZWQtdG8gYXJ0aXN0cy4gCgpJIHdvbmRlciBpZiB3ZSBjYW4gYnVpbGQgYSBtb2RlbCB0aGF0IHByZWRpY3RzIHdoZXRoZXIgYSBzb25nIGJlbG9uZ3MgdG8gRGF2aWQgQm93aWUgb3IgQW15IFdpbmVob3VzZSBiYXNlZCBvbiBpdCdzIHNwb3RpZnkgc3RhdGlzdGljczogKip2YWxlbmNlLCBkYW5jZWFiaWxpdHksIHNwZWVjaGluZXNzLCoqPwoKIyMgQ3JlYXRpbmcgb3VyIG5ldyBkYXRhc2V0CgpGaXJzdCwgbGV0J3MgZ2V0IHRoZSBkYXRhc2V0IHdlJ2xsIGJlIHdvcmtpbmcgd2l0aCBieSB1c2luZyBgcmJpbmQoKWAgdG8gYWRkIHRoZSBgYm93aWVgIGRhdGEgdG8gdGhlIGB3aW5laG91c2VgIGRhdGEuIFdlIGFsc28gaGF2ZSB0byBmYWN0b3IgYGFydGlzdF9uYW1lYCBpbiBvcmRlciBmb3IgdGhlIGNsYXNzaWZpY2F0aW9uIG1vZGVscyB0byB3b3JrIGNvcnJlY3RseS4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KYndqb2luZWQgPSByYmluZCh3aW5laG91c2UsIGJvd2llKQoKYndqb2luZWQgPSBid2pvaW5lZCAlPiUgCiAgbXV0YXRlKGFydGlzdF9uYW1lID0gZmFjdG9yKGFydGlzdF9uYW1lKSkKYGBgCgpOb3cgdGhhdCB3ZSBoYXZlIGJvdGggb2YgdGhlaXIgbXVzaWMgZGF0YSBpbiBvbmUgZGF0YXNldCwgbGV0J3Mgc3RhcnQgbWFraW5nIG91ciBtb2RlbHMhCgojIyBEZXNjaXNpb24gVHJlZQoKV2UncmUgZ29pbmcgdG8gYnVpbGQgYSAqKmRlY2lzaW9uIHRyZWUqKiBieSB1c2luZyBTcG90aWZ5J3Mgc3RhdGlzdGljcyB0byBwcmVkaWN0IHRoZSBhcnRpc3QuIFRoZSBmZWF0dXJlcyB3ZSdsbCBiZSB1c2luZyBhcmU6CgotIGBkYW5jZWFiaWxpdHlgCgotIGBlbmVyZ3lgCgotIGBsb3VkbmVzc2AKCi0gYHNwZWVjaGluZXNzYAoKLSBgYWNvdXN0aWNuZXNzYAoKLSBgaW5zdHJ1bWVudGFsbmVzc2AKCi0gYGxpdmVuZXNzYAoKLSBgdGVtcG9gCgotIGFuZCwgb2YgY291cnNlLCBgdmFsZW5jZWAhCgpOb3cgaXQncyB0aW1lIHRvIGNyZWF0ZSB0aGUgbW9kZWwuIEZpcnN0LCB3ZSBzZXQgdXAgYnkgc3BsaXR0aW5nIHRoZSBkYXRhOgoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQojIDEgRGF0YSBzcGxpdHRpbmcKc2V0LnNlZWQoMTgpICMgaXQncyBhIGdvb2QgbnVtYmVyISAKYndfc3BsaXQgPSBpbml0aWFsX3NwbGl0KGJ3am9pbmVkLCBwcm9wID0gMC43KQoKYndfdHJhaW4gPSB0cmFpbmluZyhid19zcGxpdCkKYndfdGVzdCA9IHRlc3RpbmcoYndfc3BsaXQpCmBgYAoKTm93LCB3ZSBmaXQgdGhlIG1vZGVsIHRvIGEgY2xhc3NpZmljYXRpb24gZGVjaXNpb24gdHJlZS4uLgoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQojIDIgRml0IHRoZSBtb2RlbApid190cmVlID0gCiAgZGVjaXNpb25fdHJlZSgpICU+JQogIHNldF9tb2RlKG1vZGUgPSAiY2xhc3NpZmljYXRpb24iKSAlPiUKICBmaXQoYXJ0aXN0X25hbWUgfiBkYW5jZWFiaWxpdHkgKyBlbmVyZ3kgKyBsb3VkbmVzcyArIHNwZWVjaGluZXNzICsgYWNvdXN0aWNuZXNzICsgaW5zdHJ1bWVudGFsbmVzcyArIGxpdmVuZXNzICsgdGVtcG8gKyB2YWxlbmNlLCBkYXRhID0gYndfdHJhaW4pCmBgYAoKLi4uYW5kIGdlbmVyYXRlIHByZWRpY3Rpb25zLi4uCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CiMgMyBHZW5lcmF0ZSBwcmVkaWN0aW9ucwpid19wcmVkID0gYndfdHJlZSAlPiUKICBwcmVkaWN0KG5ld19kYXRhID0gYndfdGVzdCkKYGBgCgouLi5hbmQgZmluYWxseSwgZ2VuZXJhdGUgcHJlZGljdGlvbiBtZXRyaWNzLiAKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KIyA0IFByZWRpY3Rpb24gbWV0cmljcwpid190ZXN0ICU+JQogIG11dGF0ZShhcnRpc3RfcHJlZCA9IGJ3X3ByZWQkLnByZWRfY2xhc3MpICU+JQogIGFjY3VyYWN5KGVzdGltYXRlID0gYXJ0aXN0X3ByZWQsIHRydXRoID0gYXJ0aXN0X25hbWUpCmBgYAoKVGhlIGFjY3VyYWN5IG9mIHRoaXMgbW9kZWwgaXMgKiowLjkxKiogd2hpY2ggaXMgKnZlcnkqIGhpZ2guIEJlY2F1c2UgdGhlIGFjY3VyYWN5IGlzIHNvIGhpZ2gsIGl0IG1pZ2h0IGJlIHdvcnRoIGxvb2tpbmcgYXQgYSAqKmNvbmZ1c2lvbiBtYXRyaXgqKiB0byBzZWUgaG93IHRoZSBwcmVkaWNpdG9ucyBhcmUgbGFpZCBvdXQuCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmJ3X3Rlc3QgJT4lIAogIG11dGF0ZShhcnRpc3RfcHJlZCA9IGJ3X3ByZWQkLnByZWRfY2xhc3MpICU+JQogIGNvbmZfbWF0KGVzdGltYXRlID0gYXJ0aXN0X3ByZWQsIHRydXRoID0gYXJ0aXN0X25hbWUpCmBgYAoKQWNjb3JkaW5nIHRvIHRoaXMgY29uZnVzaW9uIG1hdHJpeCwgRGF2aWQgQm93aWUgaXMgcHJlZGljdGVkIGNvcnJlY3RseSBtb3N0IG9mIHRoZSB0aW1lLCBidXQgQW15IFdpbmVob3VzZSBoYXMgYSBsb3dlciBhY2N1cmFjeS4gSXQncyBub3QgZXh0cmVtZSBhcyBzaGUncyBzdGlsbCBwcmVkaWN0ZWQgY29ycmVjdGx5IHRoZSBtYWpvcml0eSBvZiB0aGUgdGltZSwgYnV0IGl0J3Mgbm90aWNlYWJsZS4gVGhpcyBtaWdodCBiZSBiZWNhdXNlIERhdmlkIEJvd2llIGhhcyBtYW55IG1vcmUgc29uZ3MgdGhhbiBBbXkgV2luZWhvdXNlIHNvIHRoZSBtb2RlbCBpcyBtb3JlIGluY2xpbmVkIHRvIGd1ZXNzIEJvd2llIGZyb20gdGhlIHN0YXJ0LiBJZiA4MCUgb2YgdGhlIGRhdGFzZXQgY29uc2lzdHMgb2YgQm93aWUncyBtdXNpYywgZXZlbiBpZiB0aGUgbW9kZWwgZ3Vlc3NlZCBEYXZpZCBCb3dpZSBldmVyeSBzaW5nbGUgdGltZSwgaXQgd291bGQgc3RpbGwgaGF2ZSA4MCUgYWNjdXJhY3kuCgpMZXQncyBzZWUgaWYgYW55IG9mIHRoZXNlIG90aGVyIG1vZGVscyBjYW4gaW1wcm92ZSB1cG9uIHRoaXMgaXNzdWUgYnkgbGVhcm5pbmcgZnJvbSB0aGVpciBtaXN0YWtlcyAobGl0ZXJhbGx5ISkuCgojIyBCYWdnZWQgRGVjaXNpb24gVHJlZQoKQSAqKmJhZ2dlZCBkZWNpc2lvbiB0cmVlKiogaXMgYSBtb2RlbCB0aGF0IGJ1aWxkcyBtYW55IHRyZWVzIG9uIGRpZmZlcmVudCBib290c3RyYXAgc2FtcGxlcyBvZiB0aGUgdHJhaW5pbmcgZGF0YSwgd2hpY2ggaG9wZWZ1bGx5IGxvd2VycyB0aGUgbW9kZWwncyB2YXJpYW5jZSBhbmQga2VlcHMgYmlhcyBsb3cuCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CiMgMSBEYXRhIHNwbGl0dGluZwpzZXQuc2VlZCgxOCkgIyBpdCdzIGEgZ29vZCBudW1iZXIhIApid19zcGxpdCA9IGluaXRpYWxfc3BsaXQoYndqb2luZWQsIHByb3AgPSAwLjcpCgpid190cmFpbiA9IHRyYWluaW5nKGJ3X3NwbGl0KQpid190ZXN0ID0gdGVzdGluZyhid19zcGxpdCkKCiMgMiBGaXQgdGhlIG1vZGVsCmJ3X2JhZ190cmVlID0gCiAgYmFnX3RyZWUoKSAlPiUKICBzZXRfbW9kZShtb2RlID0gImNsYXNzaWZpY2F0aW9uIikgJT4lCiAgZml0KGFydGlzdF9uYW1lIH4gZGFuY2VhYmlsaXR5ICsgZW5lcmd5ICsgbG91ZG5lc3MgKyBzcGVlY2hpbmVzcyArIGFjb3VzdGljbmVzcyArIGluc3RydW1lbnRhbG5lc3MgKyBsaXZlbmVzcyArIHRlbXBvICsgdmFsZW5jZSwgZGF0YSA9IGJ3X3RyYWluKQoKIyAzIEdlbmVyYXRlIHByZWRpY3Rpb25zCmJ3X3ByZWQgPSBid19iYWdfdHJlZSAlPiUKICBwcmVkaWN0KG5ld19kYXRhID0gYndfdGVzdCkKCiMgNCBQcmVkaWN0aW9uIG1ldHJpY3MKYndfdGVzdCAlPiUKICBtdXRhdGUoYXJ0aXN0X3ByZWQgPSBid19wcmVkJC5wcmVkX2NsYXNzKSAlPiUKICBhY2N1cmFjeShlc3RpbWF0ZSA9IGFydGlzdF9wcmVkLCB0cnV0aCA9IGFydGlzdF9uYW1lKQoKYndfdGVzdCAlPiUKICBtdXRhdGUoYXJ0aXN0X3ByZWQgPSBid19wcmVkJC5wcmVkX2NsYXNzKSAlPiUKICBjb25mX21hdChlc3RpbWF0ZSA9IGFydGlzdF9wcmVkLCB0cnV0aCA9IGFydGlzdF9uYW1lKQpgYGAKCkluIHRoaXMgYmFnZ2VkIGRlY2lzaW9uIHRyZWUsIHRoZSBhY2N1cmFjeSBpcyBub3cgMC45MyBidXQgbm90IG11Y2ggaGFzIGNoYW5nZWQuIElmIGFueXRoaW5nLCBndWVzcyBhY2N1cmFjeSBmb3IgRGF2aWQgQm93aWUgaXMgaGlnaGVyIGJ1dCBBbXkgV2luZWhvdXNlJ3MgcmVtYWlucyB0aGUgc2FtZS4KCkxldCdzIHRyeSBhICoqcmFuZG9tIGZvcmVzdCoqIG5leHQuIFNpbWlsYXIgdG8gYSBiYWdnZWQgZGVjaXNpb24gdHJlZSwgcmFuZG9tIGZvcmVzdHMgdXNlIGEgKnJhbmRvbSBzdWJzZXQgb2YgZmVhdHVyZXMqIGF0IGVhY2ggc3BsaXQgaW4gdGhlIHRyZWUuCgojIyBSYW5kb20gRm9yZXN0CgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CiMgMSBEYXRhIHNwbGl0dGluZwpzZXQuc2VlZCgxOCkgIyBpdCdzIGEgZ29vZCBudW1iZXIhIApid19zcGxpdCA9IGluaXRpYWxfc3BsaXQoYndqb2luZWQsIHByb3AgPSAwLjcpCgpid190cmFpbiA9IHRyYWluaW5nKGJ3X3NwbGl0KQpid190ZXN0ID0gdGVzdGluZyhid19zcGxpdCkKCiMgMiBGaXQgdGhlIG1vZGVsCmJ3X3JmID0gCiAgcmFuZF9mb3Jlc3QoKSAlPiUKICBzZXRfbW9kZShtb2RlID0gImNsYXNzaWZpY2F0aW9uIikgJT4lCiAgZml0KGFydGlzdF9uYW1lIH4gZGFuY2VhYmlsaXR5ICsgZW5lcmd5ICsgbG91ZG5lc3MgKyBzcGVlY2hpbmVzcyArIGFjb3VzdGljbmVzcyArIGluc3RydW1lbnRhbG5lc3MgKyBsaXZlbmVzcyArIHRlbXBvICsgdmFsZW5jZSwgZGF0YSA9IGJ3X3RyYWluKQoKIyAzIEdlbmVyYXRlIHByZWRpY3Rpb25zCmJ3X3ByZWQgPSBid19yZiAlPiUKICBwcmVkaWN0KG5ld19kYXRhID0gYndfdGVzdCkKCiMgNCBQcmVkaWN0aW9uIG1ldHJpY3MKYndfdGVzdCAlPiUKICBtdXRhdGUoYXJ0aXN0X3ByZWQgPSBid19wcmVkJC5wcmVkX2NsYXNzKSAlPiUKICBhY2N1cmFjeShlc3RpbWF0ZSA9IGFydGlzdF9wcmVkLCB0cnV0aCA9IGFydGlzdF9uYW1lKQoKYndfdGVzdCAlPiUKICBtdXRhdGUoYXJ0aXN0X3ByZWQgPSBid19wcmVkJC5wcmVkX2NsYXNzKSAlPiUKICBjb25mX21hdChlc3RpbWF0ZSA9IGFydGlzdF9wcmVkLCB0cnV0aCA9IGFydGlzdF9uYW1lKQpgYGAKCldpdGggYSBzaW1pbGFyIG91dGNvbWUgYXMgdGhlIGJhZ2dlZCB0cmVlLCByYW5kb20gZm9yZXN0cyBwcm9kdWNlIGEgdmVyeSBzaW1pbGFyIG91dHB1dC4gCgojIyBHcmFkaWVudCBCb29zdGluZyBNb2RlbCAoR0JNKQoKKipHcmFkaWVudCBCb29zdGluZyBNb2RlbHMqKiAoR0JNcykgaGF2ZSBiZWNvbWUgYSB2ZXJ5IHBvcHVsYXIgbW9kZWwuIFRoaXMgaXMgYmVjYXVzZSB0aGV5IGJlZ2luIHdpdGggYSB2ZXJ5IHNpbXBsZSB0cmVlIGFuZCBsZWFybiBmcm9tIHRoZSBtaXN0YWtlcyBvZiB0aGUgcHJldmlvdXMgdHJlZSBpbiB0aGUgZW5zZW1ibGUgKHByZXZpb3VzbHkgZ2VuZXJhdGVkIHRyZWUgaW4gdGhlIEdCTSkuIFRoZXkgaGF2ZSBvcHRpb25zIHRoYXQgd2UgY2FuIHR3ZWFrIGJ1dCB3ZSdsbCBrZWVwIGl0IHNpbXBsZS4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KIyAxIERhdGEgc3BsaXR0aW5nCnNldC5zZWVkKDE4KSAjIGl0J3MgYSBnb29kIG51bWJlciEgCmJ3X3NwbGl0ID0gaW5pdGlhbF9zcGxpdChid2pvaW5lZCwgcHJvcCA9IDAuNykKCmJ3X3RyYWluID0gdHJhaW5pbmcoYndfc3BsaXQpCmJ3X3Rlc3QgPSB0ZXN0aW5nKGJ3X3NwbGl0KQoKIyAyIEZpdCB0aGUgbW9kZWwKYndfYm9vc3QgPSAKICBib29zdF90cmVlKCkgJT4lCiAgc2V0X21vZGUobW9kZSA9ICJjbGFzc2lmaWNhdGlvbiIpICU+JQogIGZpdChhcnRpc3RfbmFtZSB+IGRhbmNlYWJpbGl0eSArIGVuZXJneSArIGxvdWRuZXNzICsgc3BlZWNoaW5lc3MgKyBhY291c3RpY25lc3MgKyBpbnN0cnVtZW50YWxuZXNzICsgbGl2ZW5lc3MgKyB0ZW1wbyArIHZhbGVuY2UsIGRhdGEgPSBid190cmFpbikKCiMgMyBHZW5lcmF0ZSBwcmVkaWN0aW9ucwpid19wcmVkID0gYndfYm9vc3QgJT4lCiAgcHJlZGljdChuZXdfZGF0YSA9IGJ3X3Rlc3QpCgojIDQgUHJlZGljdGlvbiBtZXRyaWNzCmJ3X3Rlc3QgJT4lCiAgbXV0YXRlKGFydGlzdF9wcmVkID0gYndfcHJlZCQucHJlZF9jbGFzcykgJT4lCiAgYWNjdXJhY3koZXN0aW1hdGUgPSBhcnRpc3RfcHJlZCwgdHJ1dGggPSBhcnRpc3RfbmFtZSkKCmJ3X3Rlc3QgJT4lCiAgbXV0YXRlKGFydGlzdF9wcmVkID0gYndfcHJlZCQucHJlZF9jbGFzcykgJT4lCiAgY29uZl9tYXQoZXN0aW1hdGUgPSBhcnRpc3RfcHJlZCwgdHJ1dGggPSBhcnRpc3RfbmFtZSkKYGBgCgpUaGlzIG1vZGVsIGRlZmluaXRlbHkgaGFzIHRoZSBoaWdoZXN0IGFjY3VyYWN5IGF0ICoqMC45NCoqLiBUaGlzIG1vZGVsIGlzIGZhbnRhc3RpYyBhdCBwcmVkaWN0aW5nIERhdmlkIEJvd2llJ3MgbXVzaWMgYnV0IG5vdCBtdWNoIGNoYW5nZSBoYXMgaGFwcGVuZWQgd2l0aCBBbXkgV2luZWhvdXNlJ3MgbXVzaWMuIEFnYWluLCBJIGF0dHJpYnV0ZSBwYXJ0IG9mIHRoaXMgdG8gdGhlIGRpZmZlcmVuY2UgaW4gc2FtcGxlIHNpemVzIGJ1dCBoZXIgbXVzaWMgY291bGQganVzdCBiZSBtb3JlIGRpZmZpY3VsdCB0byBwcmVkaWN0LiAKCiMjIEJvbnVzOiBBZGRpbmcgTW9yZSBBcnRpc3RzIQoKSnVzdCBmb3IgZnVuLCBsZXQncyB1c2UgYSAqKkdCTSoqIGFnYWluIHRvIHByZWRpY3Qgd2hpY2ggYXJ0aXN0IGlzIHdoaWNoLi4uIGJ1dCB0cnkgaXQgYWNyb3NzICphbGwgZml2ZSogb2YgbXkgdG9wIGFydGlzdHMhCgpGaXJzdCwgdG8gc2V0IHVwIHRoZSBkYXRhc2V0OgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgR2V0IHRoZSBkYXRhIGZyb20gbXkgb3RoZXIgdGhyZWUgdG9wIGFydGlzdHMKZ29yaWxsYXogPC0gZ2V0X2FydGlzdF9hdWRpb19mZWF0dXJlcygnR29yaWxsYXonKQpncmFuZGUgPC0gZ2V0X2FydGlzdF9hdWRpb19mZWF0dXJlcygnQXJpYW5hIEdyYW5kZScpCnRoZWFkcyA8LSBnZXRfYXJ0aXN0X2F1ZGlvX2ZlYXR1cmVzKCdUYWxraW5nIEhlYWRzJykKCiMgQ29tYmluZSBhbGwgb2YgdGhlIGFydGlzdHMnIGRhdGEKYWxsX2FydGlzdHMgPSByYmluZCh3aW5laG91c2UsIGJvd2llLCBnb3JpbGxheiwgZ3JhbmRlLCB0aGVhZHMpCgojIEZhY3RvciBhcnRpc3RfbmFtZSB0byB1c2UgaXQgZm9yIG91ciBHQk0hCmFsbF9hcnRpc3RzID0gYWxsX2FydGlzdHMgJT4lIAogIG11dGF0ZShhcnRpc3RfbmFtZSA9IGZhY3RvcihhcnRpc3RfbmFtZSkpCmBgYAoKTm93IHRoYXQgdGhlIGRhdGEgaXMgYWxsIHNldCwgd2UgY2FuIGZpdCBvdXIgR0JNIQoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgMSBEYXRhIHNwbGl0dGluZwpzZXQuc2VlZCgxOCkgIyBpdCdzIGEgZ29vZCBudW1iZXIhIAphbGxfc3BsaXQgPSBpbml0aWFsX3NwbGl0KGFsbF9hcnRpc3RzLCBwcm9wID0gMC43KQoKYWxsX3RyYWluID0gdHJhaW5pbmcoYWxsX3NwbGl0KQphbGxfdGVzdCA9IHRlc3RpbmcoYWxsX3NwbGl0KQoKIyAyIEZpdCB0aGUgbW9kZWwKYWxsX2Jvb3N0ID0gCiAgYm9vc3RfdHJlZSgpICU+JQogIHNldF9tb2RlKG1vZGUgPSAiY2xhc3NpZmljYXRpb24iKSAlPiUKICBmaXQoYXJ0aXN0X25hbWUgfiBkYW5jZWFiaWxpdHkgKyBlbmVyZ3kgKyBsb3VkbmVzcyArIHNwZWVjaGluZXNzICsgYWNvdXN0aWNuZXNzICsgaW5zdHJ1bWVudGFsbmVzcyArIGxpdmVuZXNzICsgdGVtcG8gKyB2YWxlbmNlLCBkYXRhID0gYWxsX3RyYWluKQoKIyAzIEdlbmVyYXRlIHByZWRpY3Rpb25zCmFsbF9wcmVkID0gYWxsX2Jvb3N0ICU+JQogIHByZWRpY3QobmV3X2RhdGEgPSBhbGxfdGVzdCkKCiMgNCBQcmVkaWN0aW9uIG1ldHJpY3MKYWxsX3Rlc3QgJT4lCiAgbXV0YXRlKGFydGlzdF9wcmVkID0gYWxsX3ByZWQkLnByZWRfY2xhc3MpICU+JQogIGFjY3VyYWN5KGVzdGltYXRlID0gYXJ0aXN0X3ByZWQsIHRydXRoID0gYXJ0aXN0X25hbWUpCgphbGxfdGVzdCAlPiUKICBtdXRhdGUoYXJ0aXN0X3ByZWQgPSBhbGxfcHJlZCQucHJlZF9jbGFzcykgJT4lCiAgY29uZl9tYXQoZXN0aW1hdGUgPSBhcnRpc3RfcHJlZCwgdHJ1dGggPSBhcnRpc3RfbmFtZSkKYGBgCgpUaGUgYWNjdXJhY3kgaXMgKiowLjg0KiouIFRoYXQncyBub3QgYWxsIHRoYXQgYmFkLCBhY3R1YWxseSEgSW4gdGhpcyBjYXNlLCBzaW5jZSB3ZSBoYXZlIHNvIG1hbnkgbW9yZSB0YXJnZXRzIChhcnRpc3RzKSwgdGhlIGNvbmZ1c2lvbiBtYXRyaXggc3VpdHMgaXQgbmFtZSDigJQgaXQncyBwcmV0dHkgY29uZnVzaW5nLiBTdGlsbCwgSSBjYW4gc2VlIHRoYXQgQXJpYW5hIEdyYW5kZSBhbmQgRGF2aWQgQm93aWUgaGF2ZSB0aGUgaGlnaGVzdCBhY2N1cmFjeS4gSW4gKm9uZSBtb3JlIHRlc3QqLCBJIHdvbmRlciBob3cgYSBtb2RlbCB3b3VsZCBkbyBpZiBpdCBqdXN0IGhhZCB0byBsb29rIGF0IHRob3NlIHR3byBtdXNpY2lhbnM/CgojIyBCb251cyBJSTogSSBDYW4ndCBTdG9wIE1vZGVsaW5nIQoKTGV0J3MganVzdCBkbyB0aGlzIGFsbCBpbiBvbmUgZ286CgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBDb21iaW5lIGFsbCBvZiB0aGUgYXJ0aXN0cycgZGF0YQpib3dpZV9ncmFuZGUgPSByYmluZChib3dpZSwgZ3JhbmRlKQoKIyBGYWN0b3IgYXJ0aXN0X25hbWUgdG8gdXNlIGl0IGZvciBvdXIgR0JNIQpib3dpZV9ncmFuZGUgPSBib3dpZV9ncmFuZGUgJT4lIAogIG11dGF0ZShhcnRpc3RfbmFtZSA9IGZhY3RvcihhcnRpc3RfbmFtZSkpCgojIDEgRGF0YSBzcGxpdHRpbmcKc2V0LnNlZWQoMTgpICMgaXQncyBhIGdvb2QgbnVtYmVyISAKYmdfc3BsaXQgPSBpbml0aWFsX3NwbGl0KGJvd2llX2dyYW5kZSwgcHJvcCA9IDAuNykKCmJnX3RyYWluID0gdHJhaW5pbmcoYmdfc3BsaXQpCmJnX3Rlc3QgPSB0ZXN0aW5nKGJnX3NwbGl0KQoKIyAyIEZpdCB0aGUgbW9kZWwKYmdfYm9vc3QgPSAKICBib29zdF90cmVlKCkgJT4lCiAgc2V0X21vZGUobW9kZSA9ICJjbGFzc2lmaWNhdGlvbiIpICU+JQogIGZpdChhcnRpc3RfbmFtZSB+IGRhbmNlYWJpbGl0eSArIGVuZXJneSArIGxvdWRuZXNzICsgc3BlZWNoaW5lc3MgKyBhY291c3RpY25lc3MgKyBpbnN0cnVtZW50YWxuZXNzICsgbGl2ZW5lc3MgKyB0ZW1wbyArIHZhbGVuY2UsIGRhdGEgPSBiZ190cmFpbikKCiMgMyBHZW5lcmF0ZSBwcmVkaWN0aW9ucwpiZ19wcmVkID0gYmdfYm9vc3QgJT4lCiAgcHJlZGljdChuZXdfZGF0YSA9IGJnX3Rlc3QpCgojIDQgUHJlZGljdGlvbiBtZXRyaWNzCmJnX3Rlc3QgJT4lCiAgbXV0YXRlKGFydGlzdF9wcmVkID0gYmdfcHJlZCQucHJlZF9jbGFzcykgJT4lCiAgYWNjdXJhY3koZXN0aW1hdGUgPSBhcnRpc3RfcHJlZCwgdHJ1dGggPSBhcnRpc3RfbmFtZSkKCmJnX3Rlc3QgJT4lCiAgbXV0YXRlKGFydGlzdF9wcmVkID0gYmdfcHJlZCQucHJlZF9jbGFzcykgJT4lCiAgY29uZl9tYXQoZXN0aW1hdGUgPSBhcnRpc3RfcHJlZCwgdHJ1dGggPSBhcnRpc3RfbmFtZSkKYGBgCgoqKjAuOTggYWNjdXJhY3khISEqKiBUaGF0J3MgZGFybiBnb29kLCBlc3BlY2lhbGx5IHNpbmNlIHRoZSBjb25mdXNpb24gbWF0cml4IGlzbid0IHNrZXRjaHkgYWJvdXQgaXQhIAoKIyBDb25jbHVzaW9uCgpUaHJvdWdob3V0IHRoaXMgZmluYWwgcHJvamVjdCwgYSBsb3Qgb2YgY29vbCBzdGF0cyB3ZXJlIGFuYWx5emVkLiBJIGxlYXJuZWQgYWJvdXQgV2luZWhvdXNlLCBCb3dpZSwgYW5kIGhvdyBkaXN0aW5jdCBzb21lIG9mIHRoZSBtdXNpYyB0aGF0IEkgbGlzdGVuIHRvIGNhbiBiZSDigJQgRGF2aWQgQm93aWUgYW5kIEFyaWFuYSBHcmFuZGUgaW4gcGFydGljdWxhci4KCkkgaG9wZSB0aGlzIGRhdGEgYW5hbHlzaXMgd2FzIGludGVyZXN0aW5nIGFuZCBJIGVuY291cmFnZSB5b3UgdG8gYW5hbHl6ZSB5b3VyIG93biBTcG90aWZ5IGRhdGEhIFRoYW5rIHlvdSBmb3IgcmVhZGluZyB0aGlzIHBvc3QgYW5kIEkgaG9wZSB5b3UgaGF2ZSBhIGdyZWF0IGRheSEKCiFbXShodHRwczovL2NoYWJlY2hyb2QuZmlsZXMud29yZHByZXNzLmNvbS8yMDE1LzA3L3R1bWJscl9ub281bjl4dXB0MXIzcHRiZm8xXzU0MC5naWY/dz01NDAmem9vbT0yKQo=